package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.cache.CacheStats;
import com.heima.article.client.ArticleClient;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.article.thread.AppThreadLocalUtil;
import com.heima.common.cache.CacheService;
import com.heima.common.constants.ArticleConstants;
import com.heima.common.constants.BehaviorConstants;
import com.heima.common.freemarker.FreemarkerGenerator;
import com.heima.file.MinIoTemplate;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.dtos.ArticleInfoDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.article.vos.ArticleVisitStreamMess;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.search.vos.SearchArticleVo;
import com.heima.model.user.pojos.ApUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {

    @Autowired
    private ApArticleMapper apArticleMapper;

    @Autowired
    private CacheService cacheService;

    @Override
    public ResponseResult load(ArticleHomeDto dto) {
        // minBehotTime = 2063
        // publish_time < minBehotTime
        List<ApArticle> apArticles = apArticleMapper.loadArticle(dto, 1);
        return ResponseResult.okResult(apArticles);
    }

    @Override
    public ResponseResult load2(ArticleHomeDto dto) {
        if (dto == null) {
            log.warn("入参不能为空");
            return ResponseResult.okResult("");
        }

        String tag = dto.getTag();

        // 查缓存
        String hotArticleCache = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + tag);
        if (StringUtils.isEmpty(hotArticleCache)) {
            // 走原来的流程
            return load(dto);
        }

        List<ApArticle> apArticles = JSON.parseArray(hotArticleCache, ApArticle.class);
        return ResponseResult.okResult(apArticles);
    }

    @Override
    public ResponseResult loadmore(ArticleHomeDto dto) {
        // publish_time < minBehotTime
        List<ApArticle> apArticles = apArticleMapper.loadArticle(dto, 1);
        return ResponseResult.okResult(apArticles);
    }

    @Override
    public ResponseResult loadnew(ArticleHomeDto dto) {
        // publish_time > maxBehotTime
        List<ApArticle> apArticles = apArticleMapper.loadArticle(dto, 2);
        return ResponseResult.okResult(apArticles);
    }

    @Autowired
    private ApArticleContentMapper apArticleContentMapper;

    @Autowired
    private ApArticleConfigMapper apArticleConfigMapper;

    @Autowired
    private FreemarkerGenerator freemarkerGenerator;

    @Autowired
    private MinIoTemplate minIoTemplate;

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @Override
    @Async
    public void generate(Long articleId) {
        // 1. 查询ApArticleContent表，获得里面content数据
        LambdaQueryWrapper<ApArticleContent> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ApArticleContent::getArticleId, articleId);
        ApArticleContent apArticleContent = apArticleContentMapper.selectOne(wrapper);
        if (apArticleContent == null) {
            log.warn("文章内容不存在");
            return;
        }

        // 2. 创建一个数据集合Map，把上一步content数据赋值到map的content字段上
        Map<String, Object> map = new HashMap<>(1);
        map.put("content", JSON.parseArray(apArticleContent.getContent()));

        // 3. 调用Freemarker插件，生成HTML页面
        InputStream is = freemarkerGenerator.generate("article.ftl", map);

        // 4. 调用MinIO插件，上传HTML页面
        String url = minIoTemplate.uploadFile("", articleId + ".html", is, "html");

        ApArticle apArticle = apArticleMapper.selectById(articleId);
        if (apArticle == null) {
            log.warn("文章为空");
            return;
        }

        apArticle.setStaticUrl(url);

        // 5. 更新ApArticle，修改里面static_url字段
        apArticleMapper.updateById(apArticle);
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public ResponseResult saveArticle(ArticleDto dto) {
        // 1. dto判空
        if (dto == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_REQUIRE);
        }

        Long id = dto.getId();
        String content = dto.getContent();

        // 组装article表数据
        ApArticle apArticle = new ApArticle();
        BeanUtils.copyProperties(dto, apArticle);

        // 2. 判断dto里面的id是否存在
        if (id == null) {
            // 3. 不存在 -> 新增

            // 3.1 插入article表
            int insertResult = apArticleMapper.insert(apArticle);
            if (insertResult < 1) {
                return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "文章新增失败");
            }

            // 3.2 插入article_config表
            ApArticleConfig config = new ApArticleConfig();
            config.setIsDown(false);
            config.setIsDelete(false);
            config.setIsForward(true);
            config.setIsComment(true);
            config.setArticleId(apArticle.getId());
            int configInsertResult = apArticleConfigMapper.insert(config);
            if (configInsertResult < 1) {
                log.warn("文章配置表插入失败");
                throw new RuntimeException("文章配置表插入失败");
            }

            // 3.3 插入article_content表
            ApArticleContent articleContent = new ApArticleContent();
            articleContent.setContent(content);
            articleContent.setArticleId(apArticle.getId());
            int contentInsertResult = apArticleContentMapper.insert(articleContent);
            if (contentInsertResult < 1) {
                log.warn("文章内容表插入失败");
                throw new RuntimeException("文章内容表插入失败");
            }

            // 调用现成的方法，实现文章详情的生成和更新
            generate(apArticle.getId());

            return ResponseResult.okResult(apArticle.getId());
        } else {
            // 4. id存在 -> 修改

            // 4.1 修改article表
            int updateResult = apArticleMapper.updateById(apArticle);
            if (updateResult < 1) {
                return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "文章修改失败");
            }

            // 4.2 修改article_content表
            ApArticleContent contentUpdate = new ApArticleContent();
            contentUpdate.setContent(content);

            LambdaQueryWrapper<ApArticleContent> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(ApArticleContent::getArticleId, id);

            int contentUpdateResult = apArticleContentMapper.update(contentUpdate, wrapper);
            if (contentUpdateResult < 1) {
                log.warn("文章内容表修改失败");
                throw new RuntimeException("文章内容表修改失败");
            }

            // 调用详情生成
            generate(id);

            // 5. 将articleId返回回去
            return ResponseResult.okResult(id);
        }
    }

    @Override
    public void sendIndexUpdate(ApArticle article, String content) {
        if (article == null || StringUtils.isBlank(content)) {
            log.warn("入参不能为空");
            return;
        }

        SearchArticleVo vo = new SearchArticleVo();
        BeanUtils.copyProperties(article, vo);
        vo.setContent(content);

        kafkaTemplate.send("es.index.update", JSON.toJSONString(vo));
    }

    @Override
    public ResponseResult loadArticleBehavior(ArticleInfoDto dto) {
        // 入参校验
        if (dto == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_REQUIRE);
        }

        // 登录校验
        ApUser user = AppThreadLocalUtil.getUser();
        if (user == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.AP_USER_DATA_NOT_EXIST);
        }

        Integer userId = user.getId();
        if (userId == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.AP_USER_DATA_NOT_EXIST);
        }

        boolean isLike = false, isUnLike = false, isCollection = false, isFollow = false;

        Long articleId = dto.getArticleId();
        Integer authorId = dto.getAuthorId();

        // 点赞
        Object liked = cacheService.hGet(BehaviorConstants.LIKE_BEHAVIOR + articleId, userId.toString());
        if (liked != null) {
            isLike = true;
        }

        // 不喜欢
        Object unLiked = cacheService.hGet(BehaviorConstants.UN_LIKE_BEHAVIOR + articleId, userId.toString());
        if (unLiked != null) {
            isUnLike = true;
        }

        // 收藏
        Object collectioned = cacheService.hGet(BehaviorConstants.COLLECTION_BEHAVIOR + articleId, userId.toString());
        if (collectioned != null) {
            isCollection = true;
        }

        // 关注
        Double followed = cacheService.zScore(BehaviorConstants.APUSER_FOLLOW_RELATION + userId, authorId.toString());
        if (followed != null) {
            isFollow = true;
        }

        Map<String, Object> result = new HashMap<>(4);
        result.put("islike", isLike);
        result.put("isunlike", isUnLike);
        result.put("iscollection", isCollection);
        result.put("isfollow", isFollow);

        return ResponseResult.okResult(result);
    }

    @Override
    public void updateScore(ArticleVisitStreamMess mess) {
        // 1. 入参检测
        if (mess == null) {
            log.warn("ArticleVisitStreamMess为空");
            return;
        }

        // 2. 更新文章四个数值
        ApArticle apArticle = updateArticleHot(mess);
        if (apArticle == null) {
            log.warn("文章不存在");
            return;
        }

        // 3. 计算当前文章的总分
        Integer totalScore = compute(apArticle);

        // 4. 查询当前文章所在频道的热门缓存
        checkHot(apArticle, totalScore);
    }


    private void checkHot(ApArticle apArticle, Integer totalScore) {
        // 查缓存
        String hotCache = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());
        if (StringUtils.isEmpty(hotCache)) {
            log.info("缓存中没有数据，直接将当前文章加入缓存");
            addArticleToCache(apArticle, totalScore, new ArrayList<>());
            return;
        }

        List<HotArticleVo> hotArticleVos = JSON.parseArray(hotCache, HotArticleVo.class);
        if (CollectionUtils.isEmpty(hotArticleVos)) {
            // 待定
            log.info("缓存中没有数据，直接将当前文章加入缓存");
            addArticleToCache(apArticle, totalScore, new ArrayList<>());
            return;
        }

        // 检查当前文章是否存在于缓存之后
        boolean flag = false;
        for (HotArticleVo vo : hotArticleVos) {
            if (vo == null) {
                continue;
            }

            if (vo.getId().equals(apArticle.getId())) {
                vo.setScore(totalScore);
                flag = true;
                break;
            }
        }

        // 当前文章不存在与缓存之中
        if (!flag) {
            // 获取缓存集合的长度
            int size = hotArticleVos.size();
            if (size < 30) {
                // 直接把当前内容写入到集合中就可以了
                addArticleToCache(apArticle, totalScore, hotArticleVos);
            } else {
                // 1. 获取30条里面，分值最小的那条
                HotArticleVo lastArticle = hotArticleVos.get(size - 1);

                // 1.1 获得对应的分数
                Integer lastScore = lastArticle.getScore();
                Integer currentScore = totalScore;

                // 2.如果当前分值大于最小那条的分值
                if (currentScore > lastScore) {
                    // 删除最小的那条
                    hotArticleVos.remove(size - 1);
                    // 把我当前这条记录刷新到缓存里
                    addArticleToCache(apArticle, totalScore, hotArticleVos);
                }
            }
        } else {
            // 3. 排序
            List<HotArticleVo> newHotArticleVoList = hotArticleVos.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());

            // 4. 写入频道缓存
            cacheService.set(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId(), JSON.toJSONString(newHotArticleVoList));

            // 5. 写入推荐频道缓存
            cacheService.set(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG, JSON.toJSONString(newHotArticleVoList));
        }
    }


    private void addArticleToCache(ApArticle article, Integer score, List<HotArticleVo> voList) {
        // 1. apArticle 转成 HotArticleVo
        HotArticleVo vo = new HotArticleVo();
        BeanUtils.copyProperties(article, vo);
        vo.setScore(score);

        // 2. add
        voList.add(vo);

        // 3. 排序
        List<HotArticleVo> newHotArticleVoList = voList.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());

        // 4. 写入频道缓存
        cacheService.set(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + article.getChannelId(), JSON.toJSONString(newHotArticleVoList));

        // 5. 写入推荐频道缓存
        cacheService.set(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG, JSON.toJSONString(newHotArticleVoList));
    }


    private Integer compute(ApArticle apArticle) {
        Integer total = 0;
        Integer views = apArticle.getViews();
        Integer likes = apArticle.getLikes();
        Integer comment = apArticle.getComment();
        Integer collection = apArticle.getCollection();

        if (views != null) {
            total += views;
        }

        if (likes != null) {
            total += likes * 3;
        }

        if (comment != null) {
            total += comment * 5;
        }

        if (collection != null) {
            total += collection * 8;
        }

        return total;
    }

    private ApArticle updateArticleHot(ArticleVisitStreamMess mess) {
        Long articleId = mess.getArticleId();

        // 1. 根据articleId查询出文章信息
        ApArticle apArticle = apArticleMapper.selectById(articleId);
        if (apArticle == null) {
            log.warn("文章" + articleId + "不存在");
            return null;
        }

        // 2. 更新四个字段
        Integer views = apArticle.getViews();
        Integer likes = apArticle.getLikes();
        Integer comment = apArticle.getComment();
        Integer collection = apArticle.getCollection();

        apArticle.setViews(views == null ? mess.getView() : views + mess.getView());
        apArticle.setLikes(likes == null ? mess.getLike() : likes + mess.getLike());
        apArticle.setComment(comment == null ? mess.getComment() : comment + mess.getComment());
        apArticle.setCollection(collection == null ? mess.getCollect() : collection + mess.getCollect());

        // 3. updateByid
        apArticleMapper.updateById(apArticle);

        return apArticle;
    }
}
